package com.crane.common.framework.jwt;



import com.crane.common.framework.utils.Jackson2JsonUtil;
import com.crane.common.framework.utils.RSAUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * @Author: zhenxing.pi
 * @Date: 2020/6/23 17:33
 * JWT 密钥的解析和加密 工具类
 */
public class JwtUtil {

    public static final String TOKEN_PFEFIX="Bearer ";
    /**
     * 对称加密密码
     */
    private static final String SECRET = "hd123456";

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, String privateKey) throws JsonProcessingException, NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] decoded = RSAUtil.decodeBase64(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, Jackson2JsonUtil.toString(userInfo))
                .setId(createJTI())
                .signWith(SignatureAlgorithm.RS256, priKey)
                .compact();
    }

    /**
     * 私钥加密token
     *
     * @param userInfo   载荷中的数据
     * @param privateKey 私钥
     * @param expire     过期时间，单位秒
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, String privateKey, int expire) throws JsonProcessingException, NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] decoded = RSAUtil.decodeBase64(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, Jackson2JsonUtil.toString(userInfo))
                .setId(createJTI())
                .setExpiration(Date.from(LocalDateTime.now().plusMinutes(expire).atZone(ZoneId.systemDefault()).toInstant()))
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return Jws<Claims>
     */
    private static Jws<Claims> parserToken(String token, String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] decoded = RSAUtil.decodeBase64(publicKey);
        PublicKey pubKey =  KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
        return Jwts.parser().setSigningKey(pubKey).parseClaimsJws(replaceTokenPrefix(token));
    }

    private static String createJTI() {
        return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * 根据公钥获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, String publicKey, Class<T> userType) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Claims body = parserToken(replaceTokenPrefix(token), publicKey).getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(Jackson2JsonUtil.toObject(body.get(JWT_PAYLOAD_USER_KEY).toString(),userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 根据公钥获取token中的用户信息
     * @param token
     * @param publicKey
     * @param valueTypeRef
     * @param <T>
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static <T> Payload<T> getInfoFromToken(String token, String publicKey, TypeReference<T> valueTypeRef) throws  NoSuchAlgorithmException, InvalidKeySpecException {
        Claims body = parserToken(replaceTokenPrefix(token), publicKey).getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(Jackson2JsonUtil.toObject(body.get(JWT_PAYLOAD_USER_KEY).toString(),valueTypeRef));
        claims.setExpiration(body.getExpiration());
        return claims;
    }


    /**
     * 对此加密生成秘钥
     * @param userInfo
     * @return
     */
    public static String generateHSToken(Object userInfo) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, Jackson2JsonUtil.toString(userInfo))
                .setId(createJTI())
                .signWith( SignatureAlgorithm.HS256, generalKey() )
                .compact();
    }

    /**
     * 对称加密解析JWT
     */
    public  static <T> Payload<T>  getInfoFromToken(String token, Class<T> userType) {
        Claims body = Jwts.parser().setSigningKey(generalKey()).parseClaimsJws(replaceTokenPrefix(token)).getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(Jackson2JsonUtil.toObject(body.get(JWT_PAYLOAD_USER_KEY).toString(),userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 对称加密解析JWT
     */
    public  static <T> Payload<T>  getInfoFromToken(String token, TypeReference<T> valueTypeRef) {
        Claims body = Jwts.parser().setSigningKey(generalKey()).parseClaimsJws(replaceTokenPrefix(token)).getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(Jackson2JsonUtil.toObject(body.get(JWT_PAYLOAD_USER_KEY).toString(),valueTypeRef));
        claims.setExpiration(body.getExpiration());
        return claims;
    }


    public static SecretKey generalKey() {
        SecretKey key = new SecretKeySpec(SECRET.getBytes(), 0, SECRET.getBytes().length, "AES");
        return key;
    }

    public static String replaceTokenPrefix(String token){
        return StringUtils.replace(token,TOKEN_PFEFIX,"");
    }

    public static void main(String[] args) throws Exception {
        Map<String,String> ss=new HashMap<>();
        ss.put("abc","1fdsfsssssssssssssssssssssssssssssssss");
        ss.put("ab","2dfffffffffffff");
        ss.put("ac","3fddddddddddddddddddddddddddddddddddddddddddddddddddd");

        Map<String, String> keyMap = RSAUtil.initKey(SECRET);
        String publicKeyString = keyMap.get("publicKeyString");
        String privateKeyString = keyMap.get("privateKeyString");


        String token= JwtUtil.generateTokenExpireInMinutes(ss,privateKeyString);
        System.out.println("token="+token);

        Payload<Map<String, String>> s= JwtUtil.getInfoFromToken(token, publicKeyString, new TypeReference<Map<String, String>>(){});
        System.out.println("s="+ Jackson2JsonUtil.toString(s));

        Map<String,String> aa=new HashMap<>();
        aa.put("userId","1111111");
        aa.put("key",String.format("%04d",new Random().nextInt(9999)));
        token= JwtUtil.generateHSToken(aa);
        System.out.println("token="+token);
        Payload<Map<String,String>> stringPayload= JwtUtil.getInfoFromToken(token, new TypeReference<Map<String, String>>(){});
        System.out.println("s="+ Jackson2JsonUtil.toString(stringPayload.getUserInfo()));


        JwtUserToken user=new JwtUserToken("1111");
        token= JwtUtil.generateHSToken(user);
        System.out.println(token);
        JwtUserToken jwtUser= JwtUtil.getInfoFromToken(token, JwtUserToken.class).getUserInfo();
        System.out.println("jwtUser="+ Jackson2JsonUtil.toString(s));

    }
}

